#!/usr/bin/python

# InnerSpace
# Copyright (C) 1999-2006 Phil Christensen
#
# $Id: auth.py 157 2007-02-11 18:37:33Z phil $
#
# See LICENSE for details

"""
Auth

Stability: stable

This module contains all the classes used in the Twisted Perspective Broker
and its authentication framework.
"""
import md5, traceback, sys, inspect

from twisted.cred import checkers, credentials, portal
from twisted.cred import error
from twisted.spread import pb
from twisted.internet import defer, reactor, protocol
from twisted.python import failure, components

from twisted.application import service

from twisted.conch import interfaces as conch_interfaces
from twisted.conch import manhole_ssh, manhole
from twisted.conch.insults import insults

from inner.space import errors, parser, editors, code, actions

from nevow import inevow, rend, tags, guard, loaders

_current_users = []
_master_service = None
_test_registry = None

def get_current_user():
	global _current_users
	if(_current_users):
		return _current_users[-1]
	return None

def user_activate(user):
	global _current_users
	_current_users.append(user)

def user_deactivate():
	global _current_users
	_current_users.pop()
	
def get_registry():
	global _test_registry
	if(_test_registry):
		return _test_registry
	global _master_service
	return _master_service.getServiceNamed('registry').registry
	
def set_test_registry(reg):
	global _test_registry
	_test_registry = reg
	
def set_master_service(service):
	global _master_service
	_master_service = service

class RegistryService(service.Service):
	"""
	Provides a service that holds a reference to the active
	registry object.
	"""
	def __init__(self, registry):
		"""
		Create a service with the given registry.
		"""
		self.registry = registry

class RegistryChecker:
	"""
	This class allows PB to authenticate itself against objects in
	the registry.
	"""
	__implements__ = checkers.ICredentialsChecker
	
	credentialInterfaces = (credentials.IUsernamePassword,
		credentials.IUsernameHashedPassword)
	
	def __init__(self, registry):
		"""
		Create a checker with the given registry.
		"""
		self.registry = registry
	
	def _cbPasswordMatch(self, matched, avatarId):
		"""
		A helper function that returns a Failure if the
		user did not authenticate correctly, and the id
		of their character object if he did.
		"""
		if matched:
			return avatarId
		else:
			return failure.Failure(error.UnauthorizedLogin())
	
	def requestAvatarId(self, credentials):
		"""
		This function is called after the user has submitted
		authentication credentials (in this case, a user name
		and password).
		"""
		refs = self.registry.refs(credentials.username)
		try:
			user = None
			system = self.registry.get(0)
			if(hasattr(credentials, 'challenge')):
				password = credentials.challenge
			else:
				password = credentials.password
			
			if(system.has_callable_verb('authenticate')):
				user = system.call_verb('authenticate', credentials.username, password)
				if(user):
					return user.get_id()
			
			user = self.registry.get(credentials.username)			
			if(not user or user.is_connected_player()):
				return failure.Failure(error.UnauthorizedLogin())
			if(user.is_player() and user.has_property("passwd", False)):
				result = credentials.checkPassword(user._vdict['passwd'].value)
				if(isinstance(result, defer.Deferred)):
					result.addCallback(self._cbPasswordMatch, user.get_id())
				elif(not isinstance(result, failure.Failure)):
					result = self._cbPasswordMatch(result, user.get_id())
				return result
		except errors.AmbiguousObjectError, e:
			return failure.Failure(e);
		return failure.Failure(error.UnauthorizedLogin())
	
class InnerSpaceRealm:
	"""
	Holds a reference to the main service object, which is, in this
	case, RegistryService.
	"""
	__implements__ = portal.IRealm
	
	def __init__(self, service, webroot=None):
		"""
		Create a realm with the given service.
		"""
		self.service = service
		self.webroot = webroot
	
	def requestAvatar(self, avatarId, mind, *interfaces):
		"""
		This function is called after the user has verified their
		identity. It returns an object that represents the user
		in the system, i.e., an avatar.
		"""
		assert avatarId is not None, "avatarId is None"
		if pb.IPerspective in interfaces:
			avatar = self._getPBAvatar(avatarId, mind)
			return pb.IPerspective, avatar, avatar.logout
		elif conch_interfaces.IConchUser in interfaces:
			return self._getSSHAvatar(avatarId)
		elif inevow.IResource in interfaces:
			return self._getWebAvatar(avatarId)
		else:
			raise NotImplementedError("no interface")
	
	def _login_code(self, user_obj):
		print user_obj, "logged in"
		user_activate(user_obj)
		
		system_object = self.service.registry.get(0)
		if(system_object.has_callable_verb("login")):
			system_object.call_verb("login", user_obj)
		
		user_deactivate()

	def _getPBAvatar(self, avatarId, mind):
		from inner.space import pbclient
		user = self.service.registry.get(avatarId)
		avatar = pbclient.UserPerspective(mind, self.service, user)
		user.set_connection(actions.RemoteWrapper(avatar))
		
		reactor.callLater(1, self._login_code, user)
		return avatar
	
	def _getSSHAvatar(self, avatarId):
		user = self.service.registry.get(avatarId)
		if not(user.is_wizard()):
			raise error.UnauthorizedLogin()
		comp = components.Componentized()
		user = manhole_ssh.TerminalUser(comp, avatarId)
		sess = manhole_ssh.TerminalSession(comp)
		
		sess.transportFactory = manhole_ssh.TerminalSessionTransport
		
		def manholeProtoFactory():
			return insults.ServerProtocol(manhole.ColoredManhole, namespace={'registry':self.service.registry})
		
		sess.chainedProtocolFactory = manholeProtoFactory
		
		comp.setComponent(conch_interfaces.IConchUser, user)
		comp.setComponent(conch_interfaces.ISession, sess)
		
		return conch_interfaces.IConchUser, user, lambda: None
	
	def _getWebAvatar(self, avatarId):
		from inner.space import webclient
		if avatarId is checkers.ANONYMOUS:
			resc = webclient.ClientLogin(self.webroot)
			resc.realm = self
			resc.logout = lambda: None
		else:
			user = self.service.registry.get(avatarId)
			resc = webclient.AuthenticatedClient(self.webroot, user, self._login_code)
			resc.realm = self
		return (inevow.IResource, resc, resc.logout)
